/**
 * @file videoEncoder.h
 * @author SDK_TEAM
 * @brief
 * @version 0.1
 * @date 2022-11-09
 *
 * Copyright:
 * © 2018 北京灵汐科技有限公司 版权所有。
 * 注意：以下内容均为北京灵汐科技有限公司原创，未经本公司允许，不得转载，否则将视为侵权；对于不遵守此声明或者其他违法使用以下内容者，本公司依法保留追究权。
 * © 2018 Lynxi Technologies Co., Ltd. All rights reserved.
 * NOTICE: All information contained here is, and remains the property of Lynxi.
 * This file can not be copied or distributed without the permission of Lynxi
 * Technologies Co., Ltd.
 *
 */
#pragma once

#include <lyn_api.h>

#include <fstream>
#include <iostream>
#include <string>

#include "framePool.hpp"
#include "util.hpp"

// 编码数据结构体，用于解码回调函数内进行数据处理
typedef struct {
  uint8_t *data;
  FramePool *pFramePool;
} SEND_ENCODE_DATA_T;

// 编码数据结构体，用于编码回调函数内进行数据处理
typedef struct {
  lynPacket_t *pPacket;        // 编码后的数据
  std::string strDstFile;      // 输出目标文件
  std::ofstream *pOutputFile;  // 输出文件
  uint64_t *pFileSize;         // 文件大小
  FramePool *pPacketPool;
} RECV_ENCODE_DATA_T;

// 编码回调处理函数
lynError_t OnRecvEncodeCallback(void *pUserData) {
  lynError_t ret = 0;
  char *pHostBuf = nullptr;
  lynRunMode_t runMode = EP;
  uint32_t uiValidSize = 0;
  uint64_t fileSize = 0;
  RECV_ENCODE_DATA_T *pData = (RECV_ENCODE_DATA_T *)pUserData;
  if (pData->pPacket->eos) {  // eos为true时，直接返回,不需要编码
    goto ENCODE_RESULT;
  }

  // 获取现在是工作在EP模式还是RC模式
  CHECK_ERR(lynGetRunMode(&runMode));

  // 获取编码packet的有效长度
  ret = lynEncGetRemotePacketValidSize(pData->pPacket, &uiValidSize);
  if (ret != 0) {
    std::cout << "lynEncGetRemotePacketValidSize error:" << ret << std::endl;
    goto ENCODE_RESULT;
  }

  if (runMode == EP) {  // 工作在EP模式下，将Device侧内存拷贝到Host侧
    pHostBuf = new char[uiValidSize];
    ret =
        lynMemcpy(pHostBuf, pData->pPacket->data, uiValidSize, ServerToClient);
    if (ret != 0) {
      std::cout << "lynMemcpy data_size error:" << ret << std::endl;
      goto ENCODE_RESULT;
    }
  } else if (runMode == RC) {  // 工作在RC模式下，直接获取对应的虚拟地址
    CHECK_ERR(lynDevPtr2VirtAddr(pData->pPacket->data, (void **)&pHostBuf));
  }

  // 将数据写入文件
  if (*pData->pFileSize < FILE_MAX_SIZE) {
    pData->pOutputFile->write(pHostBuf, uiValidSize);
    *pData->pFileSize += uiValidSize;
  }

ENCODE_RESULT:
  // 将packet对象还给内存池
  pData->pPacketPool->Push(pData->pPacket->data);
  if (runMode == EP) {
    // EP模式下释放host端内存
    if (pHostBuf != nullptr) {
      delete[] pHostBuf;
    }
  }
  delete pData;
  return ret;
}

// 视频编码类
class VideoEncoder {
 protected:
  lynStream_t m_SendStream;        // 视频编码发送流
  lynStream_t m_RecvStream;        // 视频编码接收流
  FramePool *m_pSendPool;          // 视频编码发送对象池
  FramePool *m_pRecvPool;          // 视频编码接收对象池
  bool m_bEncHeadFlag;             // 视频编码头标志
  lynVdecHandle_t m_hVencode;      // 视频编码句柄
  std::string m_strVideoOutPath;   // 视频编码保存文件路径
  std::ofstream m_outputFile;      // 输出文件
  uint64_t m_fileSize;             // 文件大小
  lynStreamCallback_t m_memcpyCB;  // 内存拷贝回调函数
  lynEvent_t m_event;

 public:
  VideoEncoder() : m_bEncHeadFlag(true), m_fileSize(0) {}

  // 根据解码输出图像信息和视频保存路径进行初始化
  void Init(lynVdecOutInfo_t vdecOutInfo, std::string strVideoOutPath,
            FramePool *sendPool, FramePool *recvPool, uint32_t width = 0,
            uint32_t height = 0) {
    // 创建视频编码流以及对象池
    CHECK_ERR(lynCreateStream(&m_SendStream));
    CHECK_ERR(lynCreateStream(&m_RecvStream));
    CHECK_ERR(lynCreateEvent(&m_event));
    m_pSendPool = sendPool;
    m_pRecvPool = recvPool;

    // 视频编码参数设置
    lynVencAttr_t tVencAttr;
    CHECK_ERR(lynVencSetDefaultParams(&tVencAttr));
    tVencAttr.level = -1;  // 设置 Level 为自动计算支持的最小的 level
    tVencAttr.codecType = LYN_CODEC_ID_H264;
    if (width > 0 && height > 0) {
      tVencAttr.width = width;
      tVencAttr.height = height;
    } else {
      tVencAttr.width = vdecOutInfo.width;
      tVencAttr.height = vdecOutInfo.height;
    }
    tVencAttr.bitdepth = 8;
    tVencAttr.bframesNum = 0;
    tVencAttr.pframesNum = 29;
    tVencAttr.inputFormat = LYN_PIX_FMT_NV12;
    tVencAttr.targetBitrate = 6000000;

    // 打开视频编码句柄
    CHECK_ERR(lynVencOpen(&m_hVencode, &tVencAttr));

    m_outputFile.open(strVideoOutPath, std::ios::binary | std::ios::out);
    m_strVideoOutPath = strVideoOutPath;
  }

  // 反初始化
  void UnInit() {
    // 关闭编码句柄以及相关流并清理相关资源
    CHECK_ERR(lynSynchronizeStream(m_SendStream));
    CHECK_ERR(lynSynchronizeStream(m_RecvStream));
    CHECK_ERR(lynVencClose(m_hVencode));
    CHECK_ERR(lynDestroyStream(m_SendStream));
    CHECK_ERR(lynDestroyStream(m_RecvStream));
    CHECK_ERR(lynDestroyEvent(m_event));
    m_outputFile.close();
    m_bEncHeadFlag = true;
  }

  void WaitForStream(lynStream_t stream) {
    //
    CHECK_ERR(lynRecordEvent(stream, m_event));
    CHECK_ERR(lynStreamWaitEvent(m_SendStream, m_event));
  }

  void EncodeImage(lynFrame_t *frame) {
    // 发送接收编码流头信息，每个编码视频发送一次
    if (m_bEncHeadFlag) {
      m_bEncHeadFlag = false;
      RECV_ENCODE_DATA_T *pEncodeData = new RECV_ENCODE_DATA_T();
      pEncodeData->strDstFile = m_strVideoOutPath;
      pEncodeData->pOutputFile = &m_outputFile;
      pEncodeData->pFileSize = &m_fileSize;
      pEncodeData->pPacket = m_pRecvPool->Pop();
      pEncodeData->pPacketPool = m_pRecvPool;
      CHECK_ERR(lynVencGetParamsSetAsync(m_RecvStream, m_hVencode,
                                         pEncodeData->pPacket));
      CHECK_ERR(lynStreamAddCallback(m_RecvStream, OnRecvEncodeCallback,
                                          pEncodeData));
    }

    // 发送编码流
    SEND_ENCODE_DATA_T *pSendEncData = new SEND_ENCODE_DATA_T();
    pSendEncData->pFramePool = m_pSendPool;
    pSendEncData->data = frame->data;
    bool bEos = frame->eos;
    CHECK_ERR(lynVencSendFrameAsync(m_SendStream, m_hVencode, frame));
    CHECK_ERR(lynStreamAddCallback(
        m_SendStream,
        [](void *userData) -> lynError_t {
          SEND_ENCODE_DATA_T *pUserDataIn =
              static_cast<SEND_ENCODE_DATA_T *>(userData);
          pUserDataIn->pFramePool->Push(pUserDataIn->data);
          delete pUserDataIn;
          return 0;
        },
        pSendEncData));

    // 发送接收编码流
    RECV_ENCODE_DATA_T *pEncodeData = new RECV_ENCODE_DATA_T();
    pEncodeData->strDstFile = m_strVideoOutPath;
    pEncodeData->pOutputFile = &m_outputFile;
    pEncodeData->pFileSize = &m_fileSize;
    pEncodeData->pPacket = m_pRecvPool->Pop();
    pEncodeData->pPacket->eos = bEos;
    pEncodeData->pPacketPool = m_pRecvPool;

    CHECK_ERR(
        lynVencRecvPacketAsync(m_RecvStream, m_hVencode, pEncodeData->pPacket));
    CHECK_ERR(lynStreamAddCallback(m_RecvStream, OnRecvEncodeCallback,
                                        pEncodeData));
  }
};